package org.fxml.helpers {
	import org.fxml.Application;
	import org.fxml.constants.Keywords;
	import org.fxml.gateways.ReflectionGateway;
	import org.fxml.utils.BooleanUtil;
	import org.fxml.utils.ObjectUtil;
	import org.fxml.utils.getDefinitionByName;

	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.utils.Proxy;
	import flash.utils.getQualifiedClassName;

	/**
	 * @author jordandoczy
	 * @private
	 */
	public class ClassBuilderFactory extends EventDispatcher {
		
		protected var _target:Application;
		
		protected var _index:uint = 0;
		protected var _paused:Boolean = false;
		protected var _XML:XML;
		
		protected var nullBuilder:AbstractBuilder;
		protected var baseBuilder:AbstractBuilder;
		protected var displayObjectContainerBuilder:AbstractBuilder;
		protected var arrayBuilder:AbstractBuilder;
		protected var spriteBuilder:AbstractBuilder;
		
		public function ClassBuilderFactory(target:Application){
			_target = target;
			createBuilders();
		}
		
		public static function castAsArray(xml:XML):XML{
			xml.@[Keywords.CLASS] = getQualifiedClassName(Array);
			delete xml.@[Keywords.FUNCTION];
			delete xml.@[Keywords.INSTANCE];
			delete xml.@[Keywords.REFERENCE];
			return xml;
		}
		
		public function create(xml : XML):* {
			_XML = xml;
			return setProperties(xml, _target);
		}
		
		public function pause():void{
			_paused = true;
		}
		
		public function resume():void{
			_paused = false;
			setProperties(_XML, _target, _index);	
		}
		
		protected function convertValue(xml:XML, builder:IBuilder=null, target:Object=null):*{
			var instance:*;
			var params:Array;
			
			if(xml.hasOwnProperty("@"+Keywords.CLASS)){
				if(xml.hasOwnProperty(Keywords.CONSTRUCTOR)) params = convertValue(castAsArray(xml.constructor[0]));
				instance =  ReflectionGateway.createInstance(xml.attribute(Keywords.CLASS), null, params);
				setProperties(xml, instance);
				return instance;
			}
			else if(xml.hasOwnProperty("@"+Keywords.INSTANCE)){
				
				if(xml.attribute(Keywords.INSTANCE).toString().indexOf("this") != -1) target = _target;
				if(!target) target = _target;
				
				instance = ObjectUtil.getValue(xml.attribute(Keywords.INSTANCE).toString(), target);
				setProperties(xml, instance);
				
				return instance;
			}
			else if(xml.hasOwnProperty("@"+Keywords.REFERENCE)){
				return getDefinitionByName(xml.attribute(Keywords.REFERENCE));
			}
			else if(xml.hasOwnProperty("@"+Keywords.FUNCTION)){
				var func:Function;
				
				if(xml.attribute(Keywords.FUNCTION).toString().indexOf("this") != -1) target = _target;
				if(!target) target = _target;
				
				try{
					func = ObjectUtil.getValue(xml.attribute(Keywords.FUNCTION).toString(), target);
				}
				catch(e:Error){
					func = getDefinitionByName(xml.attribute(Keywords.FUNCTION));
				}
				
				params = (xml.length() > 0) ? convertValue(castAsArray(xml)) : [];
				
				return func.apply(target, params);
			}
			else if(builder){
				return builder.convertValue(xml);
			}
		}
		
		protected function createBuilders():void{
			nullBuilder = new NullBuilder();
			baseBuilder = new BaseBuilder(nullBuilder);
			arrayBuilder = new ArrayBuilder(baseBuilder);
			spriteBuilder = new SpriteBuilder(baseBuilder);
		}
		
		protected function getBuilder(instance:*):IBuilder{
				 if(instance is Array)						return arrayBuilder;
			else if(instance is Sprite)						return spriteBuilder;
			else											return baseBuilder;
		}
		
		protected function setProperties(xml:XML, instance:*, index:uint=0):void{
			var builder:IBuilder = getBuilder(instance);
			var child:XML;
			
			while (index < xml.children().length()){
				if(_paused) return;
			
				child = xml.children()[index];
				
				if(builder.hasProperty(instance, child.localName()) || instance is Proxy || instance is Application || instance == _target){
					if(builder.isFunction(instance, child.localName())) {
						instance[child.localName()].apply(instance, convertValue(castAsArray(child)));
					}
					else if(child.hasOwnProperty("@"+Keywords.READ_ONLY) && BooleanUtil(child.attribute(Keywords.READ_ONLY))){
						setProperties(child, instance[child.localName()]);
					}
					else if(child.localName() != Keywords.CONSTRUCTOR) {
						builder.setProperty(instance, child.localName(), convertValue(child, builder, instance));
					}
				}
				
				index++;
				_index = index;
			}
			
			if(xml == _XML && !_paused) dispatchEvent(new Event(Event.COMPLETE));
		}
	}
}
